﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace Algorithms.Expressions
{
    public class ManualExpressionTreeEvaluator : IExpressionTreeEvaluator
    {
        #region Methods

        public double Evaluate(Expression tree, double input)
        {
            ManualExpressionTreeContext context = new ManualExpressionTreeContext(tree, input);

            return EvaluateExpression(tree, context);
        }

        #endregion // Methods

        #region Helpers

        private double EvaluateExpression(Expression expression, ManualExpressionTreeContext context)
        {
            double result;

            switch (expression.NodeType)
            {
                case ExpressionType.Add:
                    result = EvaluateAddExpression((BinaryExpression)expression, context);
                    break;
                case ExpressionType.Subtract:
                    result = EvaluateSubstractExpression((BinaryExpression)expression, context);
                    break;
                case ExpressionType.Multiply:
                    result = EvaluateMultiplyExpression((BinaryExpression)expression, context);
                    break;
                case ExpressionType.Divide:
                    result = EvaluateDivideExpression((BinaryExpression)expression, context);
                    break;
                case ExpressionType.Power:
                    result = EvaluatePowerExpression((BinaryExpression)expression, context);
                    break;
                case ExpressionType.Constant:
                    result = EvaluateConstantExpression((ConstantExpression)expression, context);
                    break;
                case ExpressionType.Parameter:
                    result = EvaluateParameterExpression((ParameterExpression)expression, context);
                    break;
                default:
                    result = EvaluateMethodCallExpression((MethodCallExpression)expression, context);
                    break;
            }

            return result;
        }

        private double EvaluateMethodCallExpression(MethodCallExpression expression, ManualExpressionTreeContext context)
        {
            object[] parameters = new object[expression.Arguments.Count];

            for (int index = 0; index < parameters.Length; index++)
            {
                Expression argument = expression.Arguments[index];
                double parameter = EvaluateExpression(argument, context);
                parameters[index] = parameter;
            }

            return (double)expression.Method.Invoke(expression.Object, parameters);
        }

        private double EvaluatePowerExpression(BinaryExpression expression, ManualExpressionTreeContext context)
        {
            double leftProduct = EvaluateExpression(expression.Left, context);
            double rightProduct = EvaluateExpression(expression.Right, context);

            return Math.Pow(leftProduct, rightProduct);
        }

        private double EvaluateDivideExpression(BinaryExpression expression, ManualExpressionTreeContext context)
        {
            double leftProduct = EvaluateExpression(expression.Left, context);
            double rightProduct = EvaluateExpression(expression.Right, context);

            return leftProduct / rightProduct;
        }

        private double EvaluateMultiplyExpression(BinaryExpression expression, ManualExpressionTreeContext context)
        {
            double leftProduct = EvaluateExpression(expression.Left, context);
            double rightProduct = EvaluateExpression(expression.Right, context);

            return leftProduct * rightProduct;
        }

        private double EvaluateParameterExpression(ParameterExpression expression, ManualExpressionTreeContext context)
        {
            return context.Input;
        }

        private double EvaluateConstantExpression(ConstantExpression expression, ManualExpressionTreeContext context)
        {
            return (double)expression.Value;
        }

        private double EvaluateSubstractExpression(BinaryExpression expression, ManualExpressionTreeContext context)
        {
            double leftProduct = EvaluateExpression(expression.Left, context);
            double rightProduct = EvaluateExpression(expression.Right, context);

            return leftProduct - rightProduct;
        }

        private double EvaluateAddExpression(BinaryExpression expression, ManualExpressionTreeContext context)
        {
            double leftProduct = EvaluateExpression(expression.Left, context);
            double rightProduct = EvaluateExpression(expression.Right, context);

            return leftProduct + rightProduct;
        }

        #endregion // Helpers
    }
}
